/****************************************************************************
 *
 * Copyright (C) 2003-2008 Los Alamos National Security, LLC
 *                         Packet Analytics Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 as
 * published by the Free Software Foundation.  You may not use, modify or
 * distribute this program under any other version of the GNU General
 * Public License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 ****************************************************************************/
package nfse.dataset.netflow;

import java.sql.*;
import java.util.*;
import java.io.*;

import nfse.*;

import java.lang.Thread;
import java.net.*;

import javax.net.*;


public class NetFlowThread extends Thread {

    private int sensor = -1;

    private int port = -1;

    private boolean quit = false;

    private String sensorName = "";

    GregorianCalendar today = null;

    private String dataDirStr = null;

    public NetFlowThread(int sensor, int port) throws Exception {
        this.sensor = sensor;
        this.port = port;
        this.sensorName = NetFSE.getSensorInfo(sensor).getName();

        /***********************************************************************
         * 1. Check to see if the port is open by creating a socket - If in use
         * assume that nfcapd is already running - Else continue 2. Start the
         * nfcapd process for the sensor name and port
         */

        dataDirStr = NetFSE.NETFLOW_BASE;
        File dataDir = new File(dataDirStr);
        if (!dataDir.exists()) {
            dataDir.mkdir();
        }
        if (!dataDir.exists()) {
            System.out.println("Could not create directory '" + dataDir.getAbsolutePath()
                    + "'. Please check file permissions and restart Net/FSE.");
            return;
        }

        dataDirStr = NetFSE.NETFLOW_BASE + this.sensorName;
        dataDir = new File(dataDirStr);
        if (!dataDir.exists()) {
            dataDir.mkdir();
        }
        if (!dataDir.exists()) {
            System.out.println("Could not create directory '" + dataDir.getAbsolutePath()
                    + "'. Please check file permissions and restart Net/FSE.");
            return;
        }

        // ServerSocketFactory ssf = ServerSocketFactory.getDefault();
        // ServerSocket ss = ssf.createServerSocket(port);
        // ss.close();
        File sh = File.createTempFile("nfcapd", ".sh");
        // System.out.println(sh.getAbsolutePath());
        PrintWriter out = new PrintWriter(new FileWriter(sh));
        String cmd = "" + NetFSE.NETFLOW_NFCAPD + " -w -D -p " + port + " -l " + dataDirStr + " ";
        // System.out.println(cmd);
        out.println(cmd);
        out.flush();
        out.close();

        System.out.println("Starting nfcapd: '" + cmd + "'");
        NetFSEUtil.execAndWait("sh " + sh.getAbsolutePath());
        System.out.println("Started nfcapd.");

        sh.delete();
    }

    public int getSensorID() {
        return sensor;
    }

    public String getCommand() {
        String cmd = "" + NetFSE.NETFLOW_NFCAPD + " -w -D -p " + port + " -l " + dataDirStr + "";
        return cmd;
    }

    public void quit() {
        try {
            quit = true;
            String exec = "ps -awx";
            Process p = Runtime.getRuntime().exec(exec);
            String cmd = getCommand();
            BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line = reader.readLine();
            while (line != null) {
                if (line.indexOf(cmd) != -1) {
                    StringTokenizer st = new StringTokenizer(line);
                    String pidStr = st.nextToken().trim();
                    int pid = Integer.parseInt(pidStr);
                    String killCmd = "kill -KILL " + pid;
                    NetFSEUtil.execAndWait(killCmd);
                }
                line = reader.readLine();
            }
            reader.close();
            p.destroy();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String toString() {
        return sensor + ", " + port;
    }

    public void run() {
        while (true) {
            if (quit)
                return;
            MetaDatabase mdb = null;
            try {

                /***************************************************************
                 * 1. Check the date, if new insert 288 entries into
                 * netflow_agent_status - Also find the last date with entries
                 * prior to today's date - Fill in gaps as needed but do nothing
                 * if there are no prior dates with entries 2. Select the oldest
                 * unprocessed file in netflow_agent_status (must be in table
                 * and exist on disk) 3. If none, sleep; else continue 4.
                 * Process file by execing 'nfdump -o long -r FILE_PATH' and
                 * send output to DataSocket 5. When complete, delete file,
                 * update entry in netflow_agent_status 6. Continue to loop
                 */

                mdb = MetaDatabase.create();
                Statement statement = mdb.createStatement();
                Statement statement2 = mdb.createStatement();

                GregorianCalendar now = new GregorianCalendar();
                now.setTimeInMillis(System.currentTimeMillis());

                if (today == null || now.after(today)) {

                    today = new GregorianCalendar();
                    today.setTimeInMillis(System.currentTimeMillis());
                    today.set(Calendar.HOUR_OF_DAY, 0);
                    today.set(Calendar.MINUTE, 0);
                    today.set(Calendar.SECOND, 0);
                    today.set(Calendar.MILLISECOND, 0);
                    today.add(Calendar.DAY_OF_MONTH, 1);
                    today.add(Calendar.SECOND, -1);

                    String dateStr = today.get(Calendar.YEAR) + "-" + (today.get(Calendar.MONTH) + 1) + "-"
                            + today.get(Calendar.DAY_OF_MONTH);

                    String sql = "select distinct Entry_Date from netflow_agent_status where Entry_Date='" + dateStr
                            + "' and sensor_id=" + sensor;
                    // System.out.println(sql);
                    ResultSet rs = statement.executeQuery(sql);
                    if (!rs.next()) { // If there are no entries we need to
                        // create them
                        rs.close();
                        for (int i = 0; i < 288; i++) {
                            sql = "insert into netflow_agent_status values (" + sensor + ",'" + dateStr + "'," + i
                                    + ",0,0)";
                            try {
                                statement.execute(sql);
                            } catch (SQLException sqle) {
                                // sqle.printStackTrace();
                            }
                        }
                    } else {
                        rs.close();
                    }
                }

                String sql = "select * from netflow_agent_status where status=0 and sensor_id=" + this.sensor;
                // System.out.println(sql);
                ResultSet rs = statement.executeQuery(sql);
                while (rs.next()) {
                    // Find the file, format is nfcapd.YYYYDDMMHHMM (Example:
                    // nfcapd.200801011105)
                    int run = rs.getInt("Run");
                    java.sql.Date date = rs.getDate("Entry_Date");
                    GregorianCalendar cal = new GregorianCalendar();
                    cal.setTimeInMillis(date.getTime());

                    int minutesToAdd = run * 5;
                    cal.add(Calendar.MINUTE, minutesToAdd);
                    // System.out.println("After adding 5*run minutes: " + new
                    // Timestamp(cal.getTime().getTime()));
                    String dir = NetFSE.NETFLOW_BASE + this.sensorName + "/";

                    int year = cal.get(Calendar.YEAR);
                    int month = cal.get(Calendar.MONTH) + 1;
                    int day = cal.get(Calendar.DAY_OF_MONTH);
                    int hour = cal.get(Calendar.HOUR_OF_DAY);
                    int minute = cal.get(Calendar.MINUTE);

                    String fileName = "nfcapd." + year;
                    if (month < 10)
                        fileName += "0";
                    fileName += month;
                    if (day < 10)
                        fileName += "0";
                    fileName += day;
                    if (hour < 10)
                        fileName += "0";
                    fileName += hour;
                    if (minute < 10)
                        fileName += "0";
                    fileName += minute;

                    if (processFile(dir, fileName)) {
                        sql = "update netflow_agent_status set status=1 where sensor_id=" + sensor
                                + " and entry_date='" + date.toString() + "' and run=" + run;
                        statement2.execute(sql);
                        Thread.sleep(1000);
                    }
                }
                rs.close();

                statement.close();
                statement2.close();
            } catch (Exception E) {
                E.printStackTrace();
                try {
                    Thread.sleep(5000);
                    // System.out.println("NetFlow thread sleeping.");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } finally {
                mdb.closeConnection();
                try {
                    Thread.sleep(5000);
                    // System.out.println("NetFlow thread sleeping.");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private boolean processFile(String dir, String fileName) {
        try {
            File file = new File(dir + fileName);
            if (!file.exists()) {
                // System.out.println("File does not exist: '" + dir +fileName);
                return false;
            }

            File sh = File.createTempFile("nfdump", ".sh");
            PrintWriter out = new PrintWriter(new FileWriter(sh));
            String cmd = "" + NetFSE.NETFLOW_NFDUMP + "  -o long -r " + file.getAbsolutePath();
            out.println(cmd);
            out.flush();
            out.close();

            SocketFactory sf = SocketFactory.getDefault();
            Socket s = sf.createSocket("localhost", NetFSE.NET_LISTENER_SERVER_PORT);
            DataOutputStream dos = new DataOutputStream(s.getOutputStream());
            DataInputStream dis = new DataInputStream(s.getInputStream());
            dos.writeInt(sensor);
            int reply = dis.readInt();
            if (reply == -1) {
                System.out.println("Sensor not supported (" + sensor + ").");
                dos.close();
                dis.close();
                s.close();
                return false;
            }

            System.out.println("Starting nfdump: '" + cmd + "'");
            Process p = Runtime.getRuntime().exec("sh " + sh.getAbsolutePath());
            BufferedReader dataIn = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line = dataIn.readLine();
            // Skip the first line as it is a header
            line = dataIn.readLine();
            while (line != null) {
                int idx = line.indexOf(" ");
                try {
                    // Check to see that the line starts with a valid date so as
                    // to filter out information lines from nfdump
                    java.sql.Date dt = java.sql.Date.valueOf(line.substring(0, idx));
                    byte[] bytes = line.getBytes();
                    dos.writeInt(bytes.length);
                    dos.write(bytes);
                } catch (Exception e) {

                }

                // System.out.println(line);

                line = dataIn.readLine();

            }
            dataIn.close();

            dos.writeInt(-1);
            dos.flush();
            dis.readInt();
            dos.close();
            dis.close();
            s.close();

            file.delete();

            return true;
        } catch (Exception E) {
            E.printStackTrace();
        }
        return false;
    }

}
